package dev.ece.suit.loader.models.ms3d;

import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import dev.ece.suit.loader.Joint;
import dev.ece.util.io.LittleEndianInputStream;
import dev.ece.util.lib.Matrix4f;
import dev.ece.util.lib.Vector3f;
import dev.ece.util.lib.Vector4f;

public class MS3DJoint implements Joint<Float> {

	private String name;
	
	private String comment;
	
	private Vector4f rotation = new Vector4f();
	
	private Vector3f position = new Vector3f();
	
	private KeyFramesRotation[] keyFramesRotations;
	
	private KeyFramesPosition[] keyFramesPositions;
	
	private MS3DJoint parent;
	
	private Matrix4f skeleton = new Matrix4f();
	
	private Matrix4f globalMatrix = new Matrix4f();
	private Matrix4f offsetMatrix = new Matrix4f();
	
	private Matrix4f finalMatrix = new Matrix4f();
	private Matrix4f normalMatrix = new Matrix4f();
	
	private MS3DJoint() {}
	
	public static MS3DJoint[] load(LittleEndianInputStream is) throws IOException {
		MS3DJoint[] joints = new MS3DJoint[is.readUnsignedShort()];
		Map<String, MS3DJoint> jointsMap = new ConcurrentHashMap<String, MS3DJoint>();
		MS3DJoint joint = null;
		for(int i=0; i<joints.length; i++) {
			joints[i] = joint = new MS3DJoint();
			is.skip(1);
			joint.name = is.readString(32);
			String parentName = is.readString(32);
			joint.rotation.fromEulerToQuaternion(is.readFloat(), is.readFloat(), is.readFloat());
			joint.position.set(is.readFloat(), is.readFloat(), is.readFloat());
			joint.keyFramesRotations = new KeyFramesRotation[is.readUnsignedShort()];
			joint.keyFramesPositions = new KeyFramesPosition[is.readUnsignedShort()];
			KeyFramesRotation.load(is, joint.keyFramesRotations);
			KeyFramesPosition.load(is, joint.keyFramesPositions);
			jointsMap.put(joint.name, joint);
			joint.parent = jointsMap.get(parentName);
			joint.setup();
		}
		return joints;
	}
	
	public static void loadComment(LittleEndianInputStream is, MS3DJoint[] joints) throws IOException {
		int count = is.readInt();
		MS3DJoint joint = null;
		long commentSize = 0;
		for(int i=0; i<count; i++) {
			joint = joints[is.readInt()];
			commentSize = is.readUnsignedInt();
			if(commentSize > 0) {
				joint.comment = is.readString((int) commentSize);
			}
		}
	}
	
	protected void setup() {
		this.skeleton.identity();
		this.skeleton.rotate(rotation);
		this.skeleton.translate(position);
		//临时使用finalTransform矩阵
		Matrix4f skeleton = getGlobalTransform();
		skeleton.set(this.skeleton);
		if(this.hasParent()) {
			skeleton.mul(this.getParent().getGlobalTransform(), skeleton);
		}
		this.getOffsetTransform().set(skeleton);
		this.getOffsetTransform().invert();
		//初始化位置
		this.getFinalTransform().mul(this.getGlobalTransform(), this.getOffsetTransform());
		this.getNormalTransform().set(this.getFinalTransform());
		this.getNormalTransform().invert().transpose();
	}
	
	public static class KeyFramesRotation {
		
		private float time;
		
		private Vector4f rotation;
		
		private KeyFramesRotation() {}
		
		public static void load(LittleEndianInputStream is, KeyFramesRotation[] keyFramesRotations) throws IOException {
			KeyFramesRotation keyFramesRotation = null;
			for(int i=0; i<keyFramesRotations.length; i++) {
				keyFramesRotations[i] = keyFramesRotation = new KeyFramesRotation();
				keyFramesRotation.time = is.readFloat();
				keyFramesRotation.rotation = new Vector4f();
				keyFramesRotation.rotation.fromEulerToQuaternion(is.readFloat(), is.readFloat(), is.readFloat());
			}
		}
		
		public float getTime() {
			return time;
		}
		
		public Vector4f getRotation() {
			return rotation;
		}
		
	}
	
	public static class KeyFramesPosition {
		
		private float time;
		
		private Vector3f position;
		
		private KeyFramesPosition() {}
		
		public static void load(LittleEndianInputStream is, KeyFramesPosition[] keyFramesPositions) throws IOException {
			KeyFramesPosition keyFramesPosition = null;
			for(int i=0; i<keyFramesPositions.length; i++) {
				keyFramesPositions[i] = keyFramesPosition = new KeyFramesPosition();
				keyFramesPosition.time = is.readFloat();
				keyFramesPosition.position = new Vector3f(is.readFloat(), is.readFloat(), is.readFloat());
			}
		}

		public float getTime() {
			return time;
		}

		public Vector3f getPosition() {
			return position;
		}
		
	}
	
	@Override
	public boolean hasKeyFrames() {
		return this.keyFramesRotations != null && this.keyFramesPositions != null;
	}

	public Vector4f rotate(Float current) {
		int index = 0;
		int size = this.keyFramesRotations.length;
		Vector4f result = null;
		while(index < size && this.keyFramesRotations[index].getTime() < current)
			index ++;
		if(index == 0) {
			result = this.keyFramesRotations[0].getRotation();
		} else if(index == size) {
			result = this.keyFramesRotations[size - 1].getRotation();
		} else {
			KeyFramesRotation left = this.keyFramesRotations[index - 1];
			KeyFramesRotation right = this.keyFramesRotations[index];
			result = new Vector4f();
			result.interpolate(
					left.getRotation(), 
					right.getRotation(), 
					(current - left.getTime()) / (right.getTime() - left.getTime()));
		}
		return result;
	}

	public Vector3f position(Float current) {
		int index = 0;
		int size = this.keyFramesPositions.length;
		while(index < size && this.keyFramesPositions[index].getTime() < current)
			index ++;
		if(index == 0) {
			return this.keyFramesPositions[0].getPosition();
		} else if(index == size) {
			return this.keyFramesPositions[size - 1].getPosition();
		} else {
			KeyFramesPosition left = this.keyFramesPositions[index - 1];
			KeyFramesPosition right = this.keyFramesPositions[index];
			Vector3f v = new Vector3f();
			v.interpolate(
					left.getPosition(), 
					right.getPosition(), 
					(current - left.getTime()) / (right.getTime() - left.getTime()));
			return v;
		}
	}

	@Override
	public Joint<Float> getParent() {
		return parent;
	}

	@Override
	public String getName() {
		return this.name;
	}

	public String getComment() {
		return comment;
	}

	public Vector4f getRotation() {
		return rotation;
	}

	public Vector3f getPosition() {
		return position;
	}

	public KeyFramesRotation[] getKeyFramesRotations() {
		return keyFramesRotations;
	}

	public KeyFramesPosition[] getKeyFramesPositions() {
		return keyFramesPositions;
	}

	@Override
	public Matrix4f getLocalTransform(Float current) {
		Vector4f rotation = this.rotate(current);
		Vector3f position = this.position(current);
		Matrix4f matrix = new Matrix4f();
		matrix.identity();
		matrix.rotateQuaternion(rotation);
		matrix.translate(position);
		matrix.mul(getSkeleton(), matrix);
		return matrix;
	}

	@Override
	public Matrix4f getGlobalTransform() {
		return globalMatrix;
	}

	public Matrix4f getSkeleton() {
		return skeleton;
	}

	@Override
	public Matrix4f getOffsetTransform() {
		return offsetMatrix;
	}

	@Override
	public Matrix4f getFinalTransform() {
		return finalMatrix;
	}

	@Override
	public Matrix4f getNormalTransform() {
		return normalMatrix;
	}

}
